import com.boot.ys.mybatisplus.entity.dto.FieldMeta;
import com.boot.ys.mybatisplus.entity.dto.TableMeta;
import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.*;
import java.util.*;

public class SpringBootCodeGenerator {

    // ======================== 配置参数（手动修改这里）========================
    // 项目输出根目录
    private static final String PROJECT_PATH = "E:\\123pan";
    // 项目名称
    private static final String PROJECT_NAME = "mybatisplus";
    // 基础包名
    private static final String PACKAGE_NAME = "com.boot.ys.mybatisplus";
    // 作者
    private static final String AUTHOR = "als";
    // 要生成的表名（多个用逗号分隔）
    private static final String TABLE_NAMES = "user_info";

    // ======================== 系统常量（无需修改）========================
    // 模板文件目录（resources/templates）
    private static final String TEMPLATE_DIR = "/templates";
    private static final String[] JAVA_DIRS = {"entity", "mapper", "service", "service/impl", "controller", "config"};
    private static final String[] RESOURCE_DIRS = {"mapper", "config"};

    public static void main(String[] args) throws Exception {
        SpringBootCodeGenerator generator = new SpringBootCodeGenerator();
        // 初始化FreeMarker配置
        Configuration cfg = generator.initFreeMarker();
        // 创建项目目录结构
        generator.createProjectDirs();
        // 生成基础文件（pom.xml、application.yml、启动类等）
        generator.generateBaseFiles(cfg);
        // 按表生成代码（通过实体类配置表结构）
        for (String tableName : TABLE_NAMES.split(",")) {
            // 构建表数据的假数据
            TableMeta tableMeta = generator.buildTableMeta(tableName.trim());
            // 传入实体类生成代码
            generator.generateTableCode(cfg, tableMeta);
        }
        System.out.println("✅ 项目生成完成！路径：" + PROJECT_PATH + File.separator + PROJECT_NAME);
    }

    // -------------------------- 核心：通过实体类配置表结构 --------------------------

    /**
     * 构建表元数据实体（使用 FieldMeta 和 TableMeta 配置表结构）
     */
    private TableMeta buildTableMeta(String tableName) {
        // 1. 创建表元数据实体
        TableMeta tableMeta = new TableMeta();
        // 数据库表名
        tableMeta.setTableName(tableName);
        // 表注释
        tableMeta.setTableComment(getTableComment(tableName));
        // 实体类名（首字母大写）
        tableMeta.setEntityName(underlineToCamel(tableName, true));
        tableMeta.setPackageName(PACKAGE_NAME);
        tableMeta.setAuthor(AUTHOR);
        tableMeta.setDate(new Date());
        tableMeta.setPkJavaField("id");

        // 2. 创建字段列表（通过 FieldMeta 实体配置每个字段）
        List<FieldMeta> fields = new ArrayList<>();
        // 配置 user_info 表的字段
        if ("user_info".equals(tableName)) {
            // 字段1：id（主键）
            FieldMeta idField = new FieldMeta();
            // Java字段名（驼峰）
            idField.setJavaFieldName("id");
            // Java类型
            idField.setJavaType("Long");
            // 数据库字段名（下划线）
            idField.setColumnName("id");
            // 注释
            idField.setColumnComment("用户ID（主键）");
            // 主键标识
            idField.setPrimaryKey(true);
            fields.add(idField);

            // 字段2：username
            FieldMeta usernameField = new FieldMeta();
            usernameField.setJavaFieldName("username");
            usernameField.setJavaType("String");
            usernameField.setColumnName("username");
            usernameField.setColumnComment("用户名");
            usernameField.setPrimaryKey(false);
            fields.add(usernameField);

            // 字段3：age
            FieldMeta ageField = new FieldMeta();
            ageField.setJavaFieldName("age");
            ageField.setJavaType("Integer");
            ageField.setColumnName("age");
            ageField.setColumnComment("年龄");
            ageField.setPrimaryKey(false);
            fields.add(ageField);

            // 字段4：create_time
            FieldMeta createTimeField = new FieldMeta();
            createTimeField.setJavaFieldName("createTime");
            createTimeField.setJavaType("LocalDateTime");
            createTimeField.setColumnName("create_time");
            createTimeField.setColumnComment("创建时间");
            createTimeField.setPrimaryKey(false);
            fields.add(createTimeField);

        }
        // 3. 设置字段列表和主键信息到表元数据
        tableMeta.setFields(fields);
        // 提取主键字段（从 fields 中找到 isPrimaryKey=true 的字段）
        FieldMeta pkField = fields.stream()
                .filter(FieldMeta::isPrimaryKey)
                .findFirst()
                .orElse(null);
        if (pkField != null) {
            // 主键的数据库列名
            tableMeta.setPkColumn(pkField.getColumnName());
            // 主键的Java字段名
            tableMeta.setPkJavaField(pkField.getJavaFieldName());
        }

        return tableMeta;
    }

    /**
     * 获取表注释（手动配置不同表的注释）
     */
    private String getTableComment(String tableName) {
        Map<String, String> commentMap = new HashMap<>();
        commentMap.put("user_info", "用户信息表");
        commentMap.put("order_info", "订单表");
        return commentMap.getOrDefault(tableName, tableName + "表");
    }

    // -------------------------- 以下是代码生成核心逻辑（无需修改） --------------------------

    /**
     * 初始化FreeMarker配置
     */
    private Configuration initFreeMarker() throws IOException {
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
        cfg.setClassForTemplateLoading(SpringBootCodeGenerator.class, TEMPLATE_DIR);
        cfg.setDefaultEncoding("UTF-8");
        return cfg;
    }

    /**
     * 创建项目目录结构
     */
    private void createProjectDirs() {
        String basePath = PROJECT_PATH + File.separator + PROJECT_NAME;
        // 创建Java代码目录（src/main/java/包路径/...）
        String javaBasePath = basePath + File.separator + "src/main/java/" + PACKAGE_NAME.replace(".", File.separator);
        for (String dir : JAVA_DIRS) {
            new File(javaBasePath + File.separator + dir).mkdirs();
        }
        // 创建资源文件目录（src/main/resources/...）
        String resourceBasePath = basePath + File.separator + "src/main/resources";
        for (String dir : RESOURCE_DIRS) {
            new File(resourceBasePath + File.separator + dir).mkdirs();
        }
        // 创建测试目录
        new File(basePath + File.separator + "src/test/java").mkdirs();
    }

    /**
     * 生成基础文件（pom.xml、application.yml、启动类等）
     */
    private void generateBaseFiles(Configuration cfg) throws Exception {
        String basePath = PROJECT_PATH + File.separator + PROJECT_NAME;
        Map<String, Object> data = new HashMap<>();
        data.put("projectName", PROJECT_NAME);
        data.put("packageName", PACKAGE_NAME);
        data.put("author", AUTHOR);
        data.put("date", new Date());
        // 传递数据库类型（如 MYSQL/ORACLE/POSTGRE_SQL）
        data.put("dbType", "MYSQL");

        // 生成pom.xml
        generateFile(cfg, "pom.ftl", data, basePath + File.separator + "pom.xml");
        // 生成application.yml（数据库配置可手动在模板中修改）
        generateFile(cfg, "application.yml.ftl", data, basePath + File.separator + "src/main/resources/application.yml");
        // 生成启动类
        String appName = PROJECT_NAME.substring(0, 1).toUpperCase() + PROJECT_NAME.substring(1) + "Application";
        data.put("appName", appName);
        generateFile(cfg, "Application.ftl", data,
                basePath + File.separator + "src/main/java/" + PACKAGE_NAME.replace(".", File.separator) + File.separator + appName + ".java");
        // 生成MyBatis-Plus配置类
        generateFile(cfg, "MyBatisPlusConfig.ftl", data,
                basePath + File.separator + "src/main/java/" + PACKAGE_NAME.replace(".", File.separator) + File.separator + "config/MyBatisPlusConfig.java");
    }

    /**
     * 按表生成业务代码（Entity、Mapper、Service、Controller）
     */
    private void generateTableCode(Configuration cfg, TableMeta tableMeta) throws Exception {
        // 1. 生成Entity类
        generateEntity(cfg, tableMeta);
        // 2. 生成Mapper接口和XML
        generateMapper(cfg, tableMeta);
        // 3. 生成Service接口和实现类
        generateService(cfg, tableMeta);
        // 4. 生成Controller类
        generateController(cfg, tableMeta);
    }

    /**
     * 生成Entity类
     */
    private void generateEntity(Configuration cfg, TableMeta tableMeta) throws Exception {
        String entityPath = tableMeta.getPackageName().replace(".", File.separator) +
                File.separator + "entity" + File.separator + tableMeta.getEntityName() + ".java";
        String outputPath = PROJECT_PATH + File.separator + PROJECT_NAME +
                File.separator + "src/main/java/" + entityPath;
        generateFile(cfg, "Entity.ftl", tableMeta, outputPath);
    }

    /**
     * 生成Mapper接口和XML
     */
    private void generateMapper(Configuration cfg, TableMeta tableMeta) throws Exception {
        String mapperName = tableMeta.getEntityName() + "Mapper";
        // Mapper接口（Java文件）
        String mapperJavaPath = tableMeta.getPackageName().replace(".", File.separator) +
                File.separator + "mapper" + File.separator + mapperName + ".java";
        String mapperJavaOutputPath = PROJECT_PATH + File.separator + PROJECT_NAME +
                File.separator + "src/main/java/" + mapperJavaPath;
        generateFile(cfg, "Mapper.ftl", tableMeta, mapperJavaOutputPath);

        // Mapper XML文件
        String mapperXmlOutputPath = PROJECT_PATH + File.separator + PROJECT_NAME +
                File.separator + "src/main/resources/mapper/" + mapperName + ".xml";
        generateFile(cfg, "MapperXml.ftl", tableMeta, mapperXmlOutputPath);
    }

    /**
     * 生成Service接口和实现类
     */
    private void generateService(Configuration cfg, TableMeta tableMeta) throws Exception {
        String entityName = tableMeta.getEntityName();
        String serviceName = "I" + entityName + "Service";
        String serviceImplName = entityName + "ServiceImpl";

        // Service接口
        String servicePath = tableMeta.getPackageName().replace(".", File.separator) +
                File.separator + "service" + File.separator + serviceName + ".java";
        String serviceOutputPath = PROJECT_PATH + File.separator + PROJECT_NAME +
                File.separator + "src/main/java/" + servicePath;
        generateFile(cfg, "Service.ftl", tableMeta, serviceOutputPath);

        // Service实现类
        String serviceImplPath = tableMeta.getPackageName().replace(".", File.separator) +
                File.separator + "service/impl" + File.separator + serviceImplName + ".java";
        String serviceImplOutputPath = PROJECT_PATH + File.separator + PROJECT_NAME +
                File.separator + "src/main/java/" + serviceImplPath;
        generateFile(cfg, "ServiceImpl.ftl", tableMeta, serviceImplOutputPath);
    }

    /**
     * 生成Controller类
     */
    private void generateController(Configuration cfg, TableMeta tableMeta) throws Exception {
        String controllerName = tableMeta.getEntityName() + "Controller";
        String controllerPath = tableMeta.getPackageName().replace(".", File.separator) +
                File.separator + "controller" + File.separator + controllerName + ".java";
        String outputPath = PROJECT_PATH + File.separator + PROJECT_NAME +
                File.separator + "src/main/java/" + controllerPath;
        generateFile(cfg, "Controller.ftl", tableMeta, outputPath);
    }

    /**
     * 通用文件生成方法（模板渲染）
     */
    private void generateFile(Configuration cfg, String templateName, Object data, String outputPath) throws Exception {
        Template template = cfg.getTemplate(templateName);
        File outputFile = new File(outputPath);
        // 确保父目录存在
        if (!outputFile.getParentFile().exists()) {
            outputFile.getParentFile().mkdirs();
        }
        try (FileWriter writer = new FileWriter(outputFile)) {
            template.process(data, writer); // 将数据模型（实体类）传入模板
        }
        System.out.println("生成文件：" + outputPath);
    }

    /**
     * 下划线转驼峰（首字母是否大写）
     */
    private String underlineToCamel(String str, boolean isUpperFirst) {
        if (str == null) return null;
        str = str.toLowerCase();
        StringBuilder sb = new StringBuilder();
        boolean nextUpper = false;
        for (char c : str.toCharArray()) {
            if (c == '_') {
                nextUpper = true;
            } else {
                sb.append(nextUpper ? Character.toUpperCase(c) : c);
                nextUpper = false;
            }
        }
        String camel = sb.toString();
        return isUpperFirst ? camel.substring(0, 1).toUpperCase() + camel.substring(1) : camel;
    }
}